import logging

from django.db import transaction
from django.shortcuts import render
from fdfs_client.client import get_tracker_conf, Fdfs_client
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.mixins import CreateModelMixin
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR, HTTP_400_BAD_REQUEST
from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet

from RE_DRF.settings import alipay
from celery_tasks.email.tasks import send_mail_task
from shopping.models import Classification, Commodity, CommodityImg, ShoppingCart, Order, Payment
from shopping.serializers import ClassificationSerializer, ParentClassificationSerializer, CommoditySerializer, \
    ShoppingCartSerializer, OrderSerializer, PaymentSerializer


# Create your views here.

class ClassificationViewSet(viewsets.ModelViewSet):
    queryset = Classification.objects.all()
    serializer_class = ClassificationSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        if self.action == 'list':
            return self.queryset.filter(parent__isnull=True)

        return self.queryset

    def get_serializer_class(self):
        if self.action == 'list':
            return ParentClassificationSerializer
        else:
            return self.serializer_class

    @action(methods=['get'], detail=True)
    def commodity(self,request,pk):
        """
        得到指定类型下的所有商品
        :param request:
        :param pk:
        :return:
        """
        # 得到指定类型
        try:
            classification = self.get_queryset().get(id=pk)
        except Classification.DoesNotExist:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        classification_attr = 'classification1'
        classification_name = classification.name

        if classification.parent:
            classification_attr = 'classification2'
            classification_name = f'{classification.parent.name}--{classification.name}'

        # 反射函数  (操作对象,操作对象属性)  通过字符串操作对象的属性
        data = getattr(classification, classification_attr).filter(is_delete=False,status=1)

        return Response({
            'classification':classification_name,
            'commodity':CommoditySerializer(data,many=True).data
        })


logger = logging.getLogger(__name__)  #通过日志器把报错信息保存到文件中
#创建fastdfs客户端
tracker_path = get_tracker_conf('utils/fastdfs/client.conf')  #获取配置信息
client = Fdfs_client(tracker_path)  #初始化fastdfs对象  通过对象来进行fastdfs操作

class CommodityViewSet(viewsets.ModelViewSet):
    queryset = Commodity.objects.filter(is_delete=False)
    serializer_class = CommoditySerializer
    permission_classes = [IsAuthenticated]

    def create(self, request, *args, **kwargs):
        #因为我们需要往商品表写入数据，同时还要完成商品的图片上传写入数据，所以我们重写creat方法
        #获取用户传入数据并且校验  先将传入的数据进行反序列话操作再进行校验 校验成功就保存商品数据
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save() #保存商品数据

        #进行商品图片上传
        # 得到上传的图片文件信息
        files = request.FILES.getlist('file')  #上传的图片放到列表里面，得到所有上传的图片
        # 获取图片的文件信息
        for file in files:
            #循环获取每张图片的文件信息 上传到FDFS服务中
            # 如果有数据就继续判断数据类型是否符合规定的图片类型
            if file.content_type not in ('image/jpg', 'image/png', 'image/gif'):
                return Response(status=HTTP_400_BAD_REQUEST)
            # 如果符合图片类型再判断拓展名是否正常 不正常就报提醒并且修改
            try:
                images_ext_name = file.name.split('.')[-1]
            except Exception as e:
                logger.info('图片拓展名异常：%s' % e)  # 将异常现象保存到日志器中  这里需要导入日志器
                images_ext_name = 'png'

            # 判断文件数据是否为图片数据  把图片数据交给fastdfs，上传到storage服务上  这里需要创建fastdfs客户端
            # django不能得到图片文件只能得到图片数据
            try:
                upload_res = client.upload_by_buffer(file.read(),
                                                     file_ext_name=images_ext_name)  # 通过upload_by_buffer获取file.read()读取到的图片数据  这里还需要将文件的后缀名传进来
            except Exception as e:
                logger.error('图片上传出现异常:%s' % e)
                return Response(status=HTTP_500_INTERNAL_SERVER_ERROR)
            # 如果判断文件数据没问题就判断是否上传成功
            else:
                if upload_res.get('Status') != 'Upload successed.':
                    logger.warning('图片上传失败')
                    return Response(status=HTTP_500_INTERNAL_SERVER_ERROR)
            # 如果上面的判断都没问题  先将图片信息进行decode()解码成图片数据(不然还是字节数据)操作后 保存到request数据里面去 在模型中加入avatar字段保存图像地址数据
            image_name = upload_res.get('Remote file_id').decode()

            #保存商品图片数据
            CommodityImg.objects.create(src=image_name,commodity_id=serializer.data['id'],)
            #返回商品序列化数据
        return Response(serializer.data)

    @action(methods=['get'],detail=False)
    def optimization(self,request):
        """
        优选接口 优选评价数量前五的商品
        :param request:
        :return:
        """
        # 得到所有商品 self.get_queryset()
        queryset = self.get_queryset().order_by('-comment','-create_time')
        queryset = queryset[:5] if len(queryset) > 5 else queryset

        return Response(self.get_serializer(queryset,many=True).data)

class ShoppingCartViewSet(viewsets.ModelViewSet):
    queryset = ShoppingCart.objects.all()
    serializer_class = ShoppingCartSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        #每个人只能对自己的信息进行增删改查  所以只需通过该用户对象反向查找该用户的购物信息
        return self.request.user.shoppingcart_set.all()

    def create(self, request, *args, **kwargs):
        """
        在对购物车信息进行修改时无论传入谁的id都要覆盖成登录用户的id再进行修改
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        request.POST._mutable = True    #运行程序对用户提交信息进行修改
        request.data['user'] = request.user.id
        return ModelViewSet.create(self, request, *args, **kwargs)

    def update(self, request, *args, **kwargs):
        """
        创建也是一样
        """
        request.POST._mutable = True    #运行程序对用户提交信息进行修改
        request.data['user'] = request.user.id
        return ModelViewSet.update(self, request, *args, **kwargs)

class OrderViewSet(ReadOnlyModelViewSet,CreateModelMixin):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    permission_classes = [IsAuthenticated]

    # 确保每个人都只能增删改查自己的数据
    def get_queryset(self):
        return self.request.user.order_set.all()

    @action(methods=['put'],detail=False)
    def status(self,request):
        # 得到支付宝回调传来的数据 数据在请求地址中
        query_dict = request.query_params
        data = query_dict.dict()    # 转字典类型 用于后面查找具体信息

        # 得到订单编号和支付宝交易号
        order_id = data.get('out_trade_no')
        trade_no = data.get('trade_no')

        #重要的数据库操作之前开启事务防止数据错乱
        with transaction.atomic():
            save_point = transaction.savepoint()
            try:
                # 创建并且保存订单编号和支付宝交易号
                Payment.objects.create(
                    order_id=order_id,
                    trade_no=trade_no,
                )
                # 修改订单状态 找到准确的订单对象 再更新订单转台
                self.get_queryset().filter(order_id=order_id,status=1).update(status=2)
            except Exception as e:
                # 有问题就回滚
                transaction.savepoint_rollback(save_point)

                return Response(status=HTTP_500_INTERNAL_SERVER_ERROR)
            else:
                # 没问题就提交 并且异步发送邮件
                transaction.savepoint_commit(save_point)
                for i in self.get_queryset().get(order_id=order_id).ordergoods_set.all():
                    # 得到该订单中的每个商品信息
                    # 发送通知邮件 异步
                    send_mail_task.delay(
                        i.order.user.email,
                        f"""
                        <h2>下单成功</h2>
                       <h3>您在[Xunter]购买的[{i.commodity.name}]*{i.number}下单成功，待发货中</h3>
                        """
                    )

        return Response({'order_id':order_id,'trade_id':trade_no})



class PaymentViewSet(ReadOnlyModelViewSet):
    queryset = Payment.objects.all()
    serializer_class = PaymentSerializer

    @action(methods=['get'], detail=True)
    def pay(self,request,pk):
        """
        完成支付功能
        """
        try:
            # 得到当前用户的订单信息 并且为待支付状态
            order = Order.objects.get(order_id=pk, user=request.user, status=1)
        except Order.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        # 调用支付宝接口
        # 通过支付宝客户端对象连接支付宝的服务端
        order_string = alipay.api_alipay_trade_page_pay(    # 生成支付页面
            out_trade_no=order.order_id,
            total_amount=str(order.total_amount),   # 支付金额转字符串 1、统一格式确保传输数据时不会出现不兼容的问题2、避免浮点数精度问题3、接口规范
            subject=f'Xunter系统{order.order_id}', #标题
            return_url='http://localhost:8080/order/success',   #回调地址 支付成功后跳转到的页面 返回到前端的支付成功页面
        )
        """
        alipay.api_alipay_trade_page_pay()方法返回的是一个网址参数
        前端需要将支付宝页面网关网址与alipay.api_alipay_trade_page_pay()方法返回的网络参数进行拼接才能得到返回的网址
        """
        pay_url = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?' + order_string
        return Response({'pay_url': pay_url})